package com.fqgj.sentry.policy.engine.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fqgj.base.services.redis.RedisClient;
import com.fqgj.common.utils.StringUtils;
import com.fqgj.sentry.policy.engine.domain.Decision;
import com.fqgj.sentry.policy.engine.domain.ScoreCalculator;
import com.fqgj.sentry.policy.engine.domain.Script;
import com.fqgj.sentry.policy.engine.domain.Variable;
import com.fqgj.sentry.policy.engine.domain.VariableMap;
import com.fqgj.sentry.policy.engine.domain.VariableOrConstant;
import com.fqgj.sentry.policy.engine.domain.Weight;
import com.fqgj.sentry.policy.engine.domain.WeightRange;
import com.fqgj.sentry.policy.engine.domain.enums.OperationTypeEnum;
import com.fqgj.sentry.policy.engine.domain.enums.VariableTypeEnum;
import com.fqgj.sentry.policy.engine.eval.EmbedGroovy;
import com.fqgj.sentry.policy.engine.eval.script.ScriptEval;
import com.fqgj.sentry.policy.engine.eval.triple.TripleServiceData;
import com.fqgj.sentry.policy.engine.exception.ParserException;
import com.fqgj.sentry.policy.engine.resolver.Parser;
import com.fqgj.log.interfaces.Log;
import com.fqgj.log.factory.LogFactory;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Created by kennykong on 2017/7/18.
 */
public class Utils {

    public static final Log LOGGER = LogFactory.getLog(Parser.class);

    static final ObjectMapper json = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE);

    public static String getPath() {
        String path = Utils.class.getResource("/").toString();
        return path;
    }

    /**
     * 决策的优先级
     *
     * @param decision
     * @return
     */
    public static Integer getDecisionPriority(Decision decision) throws ParserException {
        Integer priority = null;
        if (decision != null) {
            priority = decision.getPriority();
        }
        return priority;
    }

    /**
     * 计算权重分
     *
     * @param w
     * @return
     */
    public static Double computeWeightScore(Weight w, VariableMap varMap) throws ParserException {
        String expression = w.getWeightExpression();
        List<Object> params = w.getParams();
        List<Double> targetParams = params.stream().map(p -> Double.valueOf(Utils.paramTypeConvert(p, VariableTypeEnum.DOUBLE).toString())).collect(Collectors.toList());
        Variable varExample = w.getVariable();

        Object value = 0d;
        if (varExample != null) {
            value = getVarValue(varMap, varExample);
        }
        Object o = groovyCompute(expression, value, targetParams.toArray());
        return Double.valueOf(o.toString());
    }

    /**
     * 计算評分
     *
     * @param sc
     * @return
     */
    public static Double computeScore(ScoreCalculator sc, VariableMap varMap) throws ParserException {
        String expression = sc.getExpression();
        List<Object> params = sc.getParams();
        List<Double> targetParams = params.stream().map(p -> Double.valueOf(Utils.paramTypeConvert(p, VariableTypeEnum.DOUBLE).toString())).collect(Collectors.toList());

        VariableOrConstant voc = sc.getVariable();
        Object o = null;
        try {
            // 空值处理
            if (voc == null) {
                voc = new VariableOrConstant();
                voc.setConstant(0d);
                voc.setIsVar(false);
            }
            if (voc.getIsVar()) {
                Object varValue = getVarValue(varMap, voc.getVariable());
                if (varValue == null) {
                    varValue = 0;
                }
                o = groovyCompute(expression, varValue, targetParams.toArray());
            } else {
                Object val = voc.getConstant();
                Object tVal = Utils.paramTypeConvert(val, VariableTypeEnum.DOUBLE);
                o = groovyCompute(expression, tVal, targetParams.toArray());
            }
        } catch (Exception e) {
            LOGGER.error("compute error! {}", e);
            throw new ParserException("compute error!", e);
        }

        return Double.valueOf(o.toString());
    }

    /**
     * groovy计算结果
     *
     * @param expression
     * @param varValue
     * @param params
     * @return
     */
    public static Object groovyCompute(String expression, Object varValue, Object[] params) throws ParserException {
        try {
            List<Object> pList = new ArrayList<>();
            pList.addAll(Arrays.asList(params));
            pList.add(0, varValue);
            Object o = EmbedGroovy.groovyMethodInvoke(expression, pList.toArray());
            return o;
        } catch (Exception e) {
            LOGGER.error("表达式：{}，varValue：{}，params：{}，出现异常", expression, varValue.toString(), toJsonString(params));
        }
        return null;

    }

    /**
     * 计算权重得分所在区间，得到决策
     *
     * @param weightRanges
     * @param weightScore
     * @return
     */
    public static Decision computeWeightResult(List<WeightRange> weightRanges, Double weightScore) throws ParserException {

        Predicate<WeightRange> pd = wr -> {
            Double start = wr.getStart();
            Double end = wr.getEnd();
            //含头含尾，边界取靠前面的决策
            return (weightScore >= start && weightScore <= end);
        };

        Decision result = weightRanges.stream()
                //按照起始条件排序
                .sorted(Comparator.comparingDouble(WeightRange::getStart))
                //第一个符合条件的
                .filter(pd).findFirst()
                .map(x -> x.getDecision()).get();

        return result;
    }


    /**
     * 通过示例取得变量值(lambda)
     *
     * @param varExample
     * @return
     */
    public static Object getVarValue(VariableMap varMap, Variable varExample) throws ParserException {

        Function<Variable, Object> fGetVar = (v) -> getValueFromMap(varMap, v);

        return getVariableValue(varExample, fGetVar);
    }

    /**
     * 根据varExample取得变量(递归FP)
     *
     * @param varExample
     * @param fGetVar
     * @return
     */
    public static Object getVariableValue(Variable varExample, Function<Variable, Object> fGetVar) {
        OperationTypeEnum opType = varExample.getOpType();
        if (opType == null) {
            opType = OperationTypeEnum.VARIABLE;
        }
        Object value = null;
        VariableTypeEnum varType = varExample.getVarType();

        //变量类型
        if (opType.equals(OperationTypeEnum.VARIABLE)) {
            value = fGetVar.apply(varExample);
        }

        //常量类型
        if (opType.equals(OperationTypeEnum.CONSTANT)) {
            value = varExample.getValue();
        }

        //脚本类型
        if (opType.equals(OperationTypeEnum.SCRIPT) || opType.equals(OperationTypeEnum.TRIPLE_SCRIPT)) {
            Script s = varExample.getScript();
            //入参的Example
            Map<String, Variable> params = s.getInputParams();
            Map<String, Object> paramsIn = new HashMap<>();
            for (String key : params.keySet()) {
                paramsIn.put(key, getVariableValue(params.get(key), fGetVar));
            }
            //根据Example取得入参
//            Map<String, Object> paramsIn = params.entrySet().stream().collect(Collectors.toMap(
//                    e -> e.getKey(),
//                    e -> getVariableValue(e.getValue(), fGetVar);
//            ));

            RedisClient redisClient = TripleServiceData.getRedisClient();
            //缓存  三方函数
            if (opType.equals(OperationTypeEnum.TRIPLE_SCRIPT)) {
                value = evalTripleServiceScriptWithCache(redisClient, paramsIn, varExample);
            } else {
                value = ScriptEval.evalWithCache(paramsIn, s.getScript());
            }
        }
        // 类型转换
        value = Utils.paramTypeConvert(value, varType);
        varExample.setValue(value);
        return value;
    }

    /**
     * 通过缓存取得三方调用结果
     *
     * @param redisClient
     * @param paramsIn
     * @param varExample
     * @return
     */
    private static Object evalTripleServiceScriptWithCache(RedisClient redisClient, Map paramsIn, Variable varExample) {
        Object value;
        String keyFormat = "{policySetFlowNo}_{functionCode}";
        String flowNo = paramsIn.get("flowNo").toString();
        Object temp = redisClient.get(keyFormat, flowNo, varExample.getName());
        if (temp != null) {
            value = temp;
        } else {
            value = ScriptEval.evalWithCache(paramsIn, varExample.getScript().getScript());
            redisClient.set(keyFormat, value, 300, flowNo, varExample.getName());
        }
        return value;
    }

    /**
     * @param varMap
     * @param varExample
     * @retun
     */
    public static Object getValueFromMap(VariableMap varMap, Variable varExample) {
        String varKey = varExample.getName();
        Variable var = varMap.getVariable(varKey);
        Boolean isRequired = varExample.getRequired();
        if (isRequired && var == null) {
            String error = "var is null, var key is: " + varKey;
            LOGGER.error(error);
            throw new ParserException(error);
        }
        if (!isRequired && var == null) return null;

        Object value = null;
        if (var != null) {
            value = var.getValue();
        }
        return value;
    }

    /**
     * @param path
     * @param content
     */
    public static void writeToFile(String path, String content) {

        try {
            File f = new File(path);
            if (f.exists()) {
                f.createNewFile();
            }
            FileWriter fw = new FileWriter(f, false);
            fw.write(content);
            fw.flush();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 类型转换
     *
     * @param param
     * @param targetType
     * @return
     */
    public static Object paramTypeConvert(Object param, VariableTypeEnum targetType) throws ParserException {

        if (param == null) {
            return null;
        }
        // 空字符串设置为null，防止 "" 和 null 比较出现不一致
        if (param != null && StringUtils.isEmpty(param.toString())) {
            param = null;
        }
        //如果没有类型，统一作为Object处理
        if (targetType == null) {
            targetType = VariableTypeEnum.OBJECT;
        }
        Object targetParam = param;
        if (param instanceof String) {

            if (targetType.equals(VariableTypeEnum.DOUBLE)) {
                targetParam = Double.parseDouble((String) param);
            }
            if (targetType.equals(VariableTypeEnum.INT)) {
                targetParam = Integer.parseInt((String) param);
            }
            if (targetType.equals(VariableTypeEnum.DATE)) {
                if (StringUtils.isEmpty(param.toString())) {
                    targetParam = param;
                } else {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    Date date = null;
                    try {
                        date = sdf.parse((String) param);
                    } catch (ParseException e) {
                        LOGGER.error("Parse Date Error!", e);
                        throw new ParserException("Parse Date Error!", e);
                    }
                    targetParam = date;
                }
            }
        }
        return targetParam;
    }

    /**
     * 四舍五入几位小数
     *
     * @param a
     * @param n
     * @return
     */
    public static double splitAndRound(double a, int n) {
        a = a * Math.pow(10, n);
        return (Math.round(a)) / (Math.pow(10, n));
    }

    /**
     * 对象转化为字符串
     *
     * @param o
     * @return
     */
    public static String toJsonString(Object o) {
        String result = null;
        try {
            result = json.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * json字符串转化为特定类型的对象
     *
     * @param jsonString
     * @param valueType
     * @param <T>
     * @return
     */
    public static <T> T jsonToTypedObj(String jsonString, Class<T> valueType) {
        T obj = null;
        try {
            obj = json.readValue(jsonString, valueType);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 加入系统变量
     *
     * @param params
     * @return
     */
    public static Map<String, Variable> addSystemVariable(Map<String, Variable> params) {
        String policySetFlowNo = "sys_flow_no";
        Variable var = new Variable();
        var.setVarType(VariableTypeEnum.STRING);
        var.setOpType(OperationTypeEnum.VARIABLE);
        var.setName(policySetFlowNo);
        var.setRequired(true);
        params.put(policySetFlowNo, var);
        return params;
    }

    public static void main(String[] args) {
        System.out.println(null instanceof String);
    }
}